/* These apis enable lcmgr to store OpenFlow Flow table
 * using sqlite table, and provide apis to get flow value.
 */
#include <pica_usp.hh>
#include <pica_port_map.hh>
#include <pica_lcmgr_api.hh>

#include <pica_statdb.hh>

#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <sqlite3.h>
#include <ctype_pica_lcmgr_api.h>
#ifdef __cplusplus
}
#endif


#define MAX_OF_ENTRY_LEN 120
#define MAX_SQL_LEN 1000

#define count_items(ARRAY) (sizeof(ARRAY)/sizeof(*ARRAY))

extern sqlite3 *statdb;
static sqlite3 *lcmgrdb;
static sqlite3_stmt *insertOfFlowTblStmt = 0;

static FILE* output = stdout; /* either stdout or stderr */

extern bool pica_mp_to_usp(const mp_t& mp, usp_t& usp);

typedef struct item {
    const char* name;
    int id;
    const char* type;
} Item;

static Item ofFlowsTbl[] =
{
    {"flowId", 1, "INTEGER PRIMARY KEY"},
    {"matchFields", 2, "TEXT"},
    {"matchInPort", 3, "TEXT"},
    {"outPorts", 4, "TEXT"},
    {"receivedBytes", 5, "INTEGER"},
    {"queue", 6, "INTEGER"},
    {"time", 7, "TEXT"},
};

typedef struct id_list_ {
    uint32_t id;
    struct id_list_ *next;
} IdList;
IdList *idList = NULL;

bool
pica_db_id_list_insert(uint32_t id)
{
    IdList *tmp;

    if (idList == NULL) {
        idList = (IdList *)malloc(sizeof(IdList));
        if (!idList)
            return false;
        idList->id = id;
        idList->next = NULL;
        return true;
    }
    tmp = (IdList *)malloc(sizeof(IdList));
    if (!tmp)
        return false;
    tmp->id = id;
    tmp->next = idList->next;
    idList->next = tmp;
    return true;
}

bool
pica_db_id_list_delete(uint32_t id)
{
    IdList *prev = NULL;
    IdList *cur = idList;

    while (cur) {
        if (cur->id == id) {
            if (prev == NULL) {
                idList = cur->next;
            } else {
                prev->next = cur->next;
            }
            free(cur);
            return true;
        }
        prev = cur;
        cur = cur->next;
    }
    return false;
}

bool
pica_db_get_port_name(int port, char *portName)
{
    /* in C++ world */
    usp_t usp;
    string ifname;
    int len, i;

    index_to_usp(port,usp);
    usp_to_ifname(ifname, usp);
    len = ifname.size();
    for (i=0; i<=len; i++) {
        portName[i] = ifname[i];
    }
    portName[i] = '\0';
    return true;
}

bool
pica_db_of_add_entry(uint32_t id, const of_flow_t *fe, const of_flow_qualify_udf_t *fe_udf)
{
    char fe_desc[MAX_SQL_LEN];
    char key_desc[MAX_OF_ENTRY_LEN];
    int rc;
    char portName[15];
    bool tf;
    uint32_t ifindex;

    rc = sqlite3_bind_int(insertOfFlowTblStmt, 1, id);

    snprintf(fe_desc, MAX_SQL_LEN, "{");
    if (fe->other_field.flags & OF_FIELD_SRC_MAC) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"srcMac\":\"%02x:%02x:%02x:%02x:%02x:%02x/%02x:%02x:%02x:%02x:%02x:%02x\",",
                fe->other_field.src_mac[0], fe->other_field.src_mac[1],
                fe->other_field.src_mac[2], fe->other_field.src_mac[3],
                fe->other_field.src_mac[4], fe->other_field.src_mac[5],
                fe->other_field.src_mac_mask[0],
                fe->other_field.src_mac_mask[1],
                fe->other_field.src_mac_mask[2],
                fe->other_field.src_mac_mask[3],
                fe->other_field.src_mac_mask[4],
                fe->other_field.src_mac_mask[5]);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_DST_MAC) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"dstMac\":\"%02x:%02x:%02x:%02x:%02x:%02x\",",
                fe->other_field.dst_mac[0], fe->other_field.dst_mac[1],
                fe->other_field.dst_mac[2], fe->other_field.dst_mac[3],
                fe->other_field.dst_mac[4], fe->other_field.dst_mac[5],
                fe->other_field.dst_mac_mask[0],
                fe->other_field.dst_mac_mask[1],
                fe->other_field.dst_mac_mask[2],
                fe->other_field.dst_mac_mask[3],
                fe->other_field.dst_mac_mask[4],
                fe->other_field.dst_mac_mask[5]);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_ETHER_TYPE) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"ethertype\":\"%d\",",
                fe->other_field.ether_type);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_VLAN_ID) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"vlan\":\"%d/%d\",",
                fe->other_field.vlan_id,
                fe->other_field.vlan_mask);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_VLAN_PRIORITY) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"vlanPriority\":\"%d/%d\",",
                fe->other_field.vlan_priority,
                fe->other_field.vlan_priority_mask);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_SRC_IP) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"srcIp4\":\"%d.%d.%d.%d/%d.%d.%d.%d\",",
                ((const char *)&fe->other_field.src_ip)[0],
                ((const char *)&fe->other_field.src_ip)[1],
                ((const char *)&fe->other_field.src_ip)[2],
                ((const char *)&fe->other_field.src_ip)[3],
                ((const char *)&fe->other_field.src_ip_mask)[0],
                ((const char *)&fe->other_field.src_ip_mask)[1],
                ((const char *)&fe->other_field.src_ip_mask)[2],
                ((const char *)&fe->other_field.src_ip_mask)[3]);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_DST_IP) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"dstIp4\":\"%d.%d.%d.%d/%d.%d.%d.%d\",",
                ((const char *)&fe->other_field.dst_ip)[0],
                ((const char *)&fe->other_field.dst_ip)[1],
                ((const char *)&fe->other_field.dst_ip)[2],
                ((const char *)&fe->other_field.dst_ip)[3],
                ((const char *)&fe->other_field.dst_ip_mask)[0],
                ((const char *)&fe->other_field.dst_ip_mask)[1],
                ((const char *)&fe->other_field.dst_ip_mask)[2],
                ((const char *)&fe->other_field.dst_ip_mask)[3]);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_SRC_IP6) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"srcIp6\":\"%04x:04x:%04x:%04x:%04x:%04x/%04x:04x:%04x:%04x:%04x:%04x\",",
                fe->other_field.src_ip6[0],
                fe->other_field.src_ip6[1],
                fe->other_field.src_ip6[2],
                fe->other_field.src_ip6[3],
                fe->other_field.src_ip6[4],
                fe->other_field.src_ip6[5],
                fe->other_field.src_ip6[6],
                fe->other_field.src_ip6[7],
                fe->other_field.src_ip6_mask[0],
                fe->other_field.src_ip6_mask[1],
                fe->other_field.src_ip6_mask[2],
                fe->other_field.src_ip6_mask[3],
                fe->other_field.src_ip6_mask[4],
                fe->other_field.src_ip6_mask[5],
                fe->other_field.src_ip6_mask[6],
                fe->other_field.src_ip6_mask[7]);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_DST_IP6) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"dstIp6\":\"%04x:04x:%04x:%04x:%04x:%04x/%04x:04x:%04x:%04x:%04x:%04x\",",
                fe->other_field.dst_ip6[0],
                fe->other_field.dst_ip6[1],
                fe->other_field.dst_ip6[2],
                fe->other_field.dst_ip6[3],
                fe->other_field.dst_ip6[4],
                fe->other_field.dst_ip6[5],
                fe->other_field.dst_ip6[6],
                fe->other_field.dst_ip6[7],
                fe->other_field.dst_ip6_mask[0],
                fe->other_field.dst_ip6_mask[1],
                fe->other_field.dst_ip6_mask[2],
                fe->other_field.dst_ip6_mask[3],
                fe->other_field.dst_ip6_mask[4],
                fe->other_field.dst_ip6_mask[5],
                fe->other_field.dst_ip6_mask[6],
                fe->other_field.dst_ip6_mask[7]);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_IP_PROTOCOL) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"ipProtocol\":\"%d\",",
                fe->other_field.ip_protocol);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_IP6_FLOW_LABEL) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"ip6_label\":\"0x%x/0x%x\",",
                fe->other_field.flow_label,
                fe->other_field.flow_label_mask);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_TOS) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"iptos\":\"%d\",",
                fe->other_field.tos);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_SRC_TCPUDP_PORT) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"srcPort\":\"%d\",",
                fe->other_field.src_tcpudp_port);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe->other_field.flags & OF_FIELD_DST_TCPUDP_PORT) {
        snprintf(key_desc, MAX_OF_ENTRY_LEN, "\"dstPort\":\"%d\",",
                fe->other_field.dst_tcpudp_port);
        strncat(fe_desc, key_desc, MAX_SQL_LEN);
    }
    if (fe_desc[strlen(fe_desc)-1] == ',') {
        fe_desc[strlen(fe_desc)-1] = '}';
    } else {
        strncat(fe_desc, "}", MAX_SQL_LEN);
    }
    rc = sqlite3_bind_text(insertOfFlowTblStmt, 2, fe_desc, strlen(fe_desc), 0);

    if (fe->other_field.flags & OF_FIELD_INGRESS_PORT ||
            fe->other_field.flags & OF_FIELD_INGRESS_LAG) {
        //fe.other_field.lag_id
        usp_to_index(fe->ingress_port,ifindex);
        rc = pica_db_get_port_name(ifindex, portName);
        rc = sqlite3_bind_text(insertOfFlowTblStmt, 3, portName, strlen(portName), 0);
    } else {
        rc = sqlite3_bind_text(insertOfFlowTblStmt, 3, "any", 3, 0);
    }
    if (rc != SQLITE_OK)
        return false;

    rc = sqlite3_step(insertOfFlowTblStmt);
    if (rc != SQLITE_DONE)
        return false;
    rc = sqlite3_reset(insertOfFlowTblStmt);
    if (rc != SQLITE_OK)
        return false;

    tf = pica_db_id_list_insert(id);

    return tf;
}

bool
pica_db_of_delete_entry(uint32_t id)
{
    int rc;
    bool tf;
    char sql[MAX_SQL_LEN];

    snprintf(sql, MAX_SQL_LEN, "DELETE FROM ofFlowCounters_ WHERE %s=%d", ofFlowsTbl[0].name, id);
    rc = sqlite3_exec(lcmgrdb, sql, 0, 0, 0);
    tf = pica_db_id_list_delete(id);
    return tf;
}

bool
pica_db_of_add_outport_lag(uint32_t id, usp_t &usp)
{
    char sql[MAX_SQL_LEN];
    char port_list[MAX_OF_ENTRY_LEN];
    uint32_t userport;
    int rc;

    port_list[0] = '\0';
    usp_to_index(usp,userport);
    pica_db_get_port_name(userport, port_list);
    
    snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ SET outPorts='[\"%s\"]' WHERE %s=%d", port_list, ofFlowsTbl[0].name, id);
    
    rc = sqlite3_exec(lcmgrdb, sql ,0 ,0 ,0);
    if (rc != SQLITE_OK)
        return false;
    return true;
}
bool
pica_db_of_add_outport(uint32_t id, mp_t *mp, mp_list_t *mp_list)
{
    char sql[MAX_SQL_LEN];
    char port_list[MAX_OF_ENTRY_LEN];
    mp_list_t::const_iterator it;
    usp_t usp;
    uint32_t userport;
    int rc;

    port_list[0] = '\0';
    if (mp && mp->port()) {
        if (!pica_mp_to_usp(*mp, usp)) {
            //_SDK_WARN("mp_to_usp error, port = %d", mp->port());
        }
        usp_to_index(usp,userport);
        pica_db_get_port_name(userport, port_list);
        strncat(port_list, "\",\"", MAX_OF_ENTRY_LEN);
    }
    if (mp_list) {
        for (it = mp_list->begin(); it != mp_list->end(); it++) {
            if (!pica_mp_to_usp(*it, usp)) {
                //_SDK_WARN("mp_to_usp error, port = %d", it->port());
            }
            usp_to_index(usp,userport);
            pica_db_get_port_name(userport, &port_list[strlen(port_list)]);
            strncat(port_list, "\",\"", MAX_OF_ENTRY_LEN);
        }
    }
    if (strlen(port_list) >= 3) {
        if (port_list[strlen(port_list)-2] == ',')
            port_list[strlen(port_list)-3] = '\0';
        snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ SET outPorts='[\"%s\"]' WHERE %s=%d", port_list, ofFlowsTbl[0].name, id);
    } else {
        snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ SET outPorts='[]' WHERE %s=%d", ofFlowsTbl[0].name, id);
    }
    rc = sqlite3_exec(lcmgrdb, sql ,0 ,0 ,0);
    if (rc != SQLITE_OK)
        return false;
    return true;
}

bool
pica_db_of_delete_outport(uint32_t id, mp_t *mp)
{
    int rc;
    char sql[MAX_SQL_LEN];

    if (mp == NULL) {
        /* Delete all output ports */
        snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ SET outPorts='[]' WHERE %s=%d", ofFlowsTbl[0].name, id);
        rc = sqlite3_exec(lcmgrdb, sql ,0 ,0 ,0);
    } else {
        /* delete specific port */
        /* LATER
           rc = pica_db_get_port_name(mp->port(), portName);
           rc = sqlite3_bind_int(findOfFlowTblStmt, 1, id);
           rc = sqlite3_step(findOfFlowTblStmt);
           cur_list = sqlite3_column_text(findOfFlowTblStmt, 0);
           rc = sqlite3_reset(findOfFlowTblStmt);
           findstr = strstr(cur_list, portName);
           if (findstr) {
            snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ SET outPorts='[";

            for (c = cur_list, c < findstr, c++) {
            sql
            }
           }
           */
    }

    return true;
}
bool
pica_db_of_add_queue(uint32_t id, uint32_t queue)
{
    char sql[MAX_SQL_LEN];
    int rc;

    snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ \
            SET queue=%d WHERE %s=%d", 
            queue, ofFlowsTbl[0].name, id);

    rc = sqlite3_exec(lcmgrdb, sql, 0, 0, 0);
    if (rc != SQLITE_OK) {
        return false;
    }
    return true;
}

bool
pica_db_of_delete_queue(uint32_t id, uint32_t queue)
{
    int rc;
    char sql[MAX_SQL_LEN];

    snprintf(sql, MAX_SQL_LEN, "UPDATE ofFlowCounters_ \
            SET queue=NULL WHERE %s=%d", 
            ofFlowsTbl[0].name, id);

    rc = sqlite3_exec(lcmgrdb, sql, 0, 0, 0);
    if (rc != SQLITE_OK) {
        return false;
    }
    return true;
}

bool
pica_db_prepare_create_sql(char *sql, uint32_t len,
        char *table, Item schema[], uint32_t cols)
{
    uint32_t s;

    snprintf(sql, len, "CREATE TABLE %s (", table);
    for (s = 0; s < cols; s++) {
        /* note: this is ok as schema[] is not sorted by name yet */
        if (s > 0) {
            strncat(sql, ",", len);
        }
        strncat(sql, schema[s].name, len);
        strncat(sql, " ", len);
        strncat(sql, schema[s].type, len);
    }
    strncat(sql, ")", len);
    return true;
}

bool
pica_db_prepare_insert_stmt(sqlite3_stmt **pstmt, 
        char *table, Item schema[], uint32_t cols)
{
    uint32_t s;
    int rc;
    char sql[MAX_SQL_LEN];
    uint32_t len = MAX_SQL_LEN;
    const char *tail;

    snprintf(sql, len, "INSERT INTO %s VALUES (", table);
    for (s = 0; s < cols; s++) {
        /* note: this is ok as schema[] is not sorted by name yet */
        if (s > 0)
            strncat(sql, ",:", len);
        else
            strncat(sql, ":", len);
        strncat(sql, schema[s].name, len);
    }
    strncat(sql, ")", len);

    rc = sqlite3_prepare_v2(lcmgrdb, sql, strlen(sql)+1, pstmt, &tail);
    if (rc) {
        fprintf(output, "Can't prepare insert %s table\n", table);
        return false;
    }

    return true;
}

bool
lcmgr_flowdb_init()
{
    int rc;
    char tableName[20];
    char sql[MAX_SQL_LEN];

    //rc = sqlite3_open("pica.db", &lcmgrdb);
    if (statdb != NULL) {
        lcmgrdb = statdb;
    }else {
        rc = sqlite3_open(":memory:", &lcmgrdb);
        if (rc) {
            fprintf(output, "Can't open database: %s\n", sqlite3_errmsg(lcmgrdb));
            sqlite3_close(lcmgrdb);
            return false;
        }
    }

    sprintf(tableName, "ofFlowCounters_");
    rc = pica_db_prepare_create_sql(sql, MAX_SQL_LEN, tableName, ofFlowsTbl, count_items(ofFlowsTbl));
    rc = sqlite3_exec(lcmgrdb, sql, 0, 0, 0);
    if (rc != SQLITE_OK) {
        fprintf(output, "Can't create '%s' table\n", tableName);
        sqlite3_close(lcmgrdb);
        return false;
    }
    rc = pica_db_prepare_insert_stmt(&insertOfFlowTblStmt, tableName, ofFlowsTbl , count_items(ofFlowsTbl));

    return rc;
}

IdList* 
get_flowid_list()
{
   return idList; 
}

typedef struct sql_cb_param {
    char *pvalue;
    bool isvalid;
}sql_cb_param_t;

static int
copy_out_sql_result_cb(void *p_data,
        int num_fields, char **p_fields, char **p_col_names)
{
    sql_cb_param_t *param;
    /* The parameters in this function.
     * p_data: the data pointer coller provided.
     * num_fields: should be 1
     * p_col_names[0] should be the column_name caller provided.
     */
    param = (sql_cb_param_t*)p_data;

    if (p_fields[0] != NULL) {
        param->isvalid = true;
        strcpy(param->pvalue, p_fields[0]);
        return 0;
    }
    param->isvalid = false;
    return 0;
}

bool get_offlow_column_value(uint32_t id, const char *column_name, char *retvalue)
{
    bool rc;
    char sql[MAX_SQL_LEN];
    sql_cb_param_t param;
    char *errmsg=0;

    if (column_name == NULL || retvalue == NULL)
        return false;

    snprintf(sql, MAX_SQL_LEN, "SELECT %s from ofFlowCounters_ WHERE flowId=%d",
            column_name, id);
    param.pvalue = retvalue;
    rc = pica_db_execute_sql(sql, copy_out_sql_result_cb,
            &param, &errmsg);
    if (errmsg) {
        pica_db_free((void *)errmsg);
    }
    if (!rc ||!param.isvalid)
        return false;

    return true;
}
